#include "d3d11renderer.h"
#include "dxcommon.h"
#include "renderer.h"

D3D11Renderer::D3D11Renderer(OverlayState const* pState)
  : BaseRenderer(pState)
  , pCurSwapChain(NULL)
  , pCurDevice(NULL)
  , pDeviceContext(NULL)
  , pInputLayout(NULL)
  , pVertexShader(NULL)
  , pPixelShader(NULL)
  , pVertexBuffer(NULL)
  , pIndexBuffer(NULL)
  , pConstantBuffer(NULL)
  , pBlendState(NULL)
  , pDepthState(NULL)
  , pRasterizerState(NULL)
  , pSamplerState(NULL)
{
}

void D3D11Renderer::reset()
{
  BaseRenderer::reset();
  COMRELEASE(pInputLayout);
  COMRELEASE(pVertexShader);
  COMRELEASE(pPixelShader);
  COMRELEASE(pVertexBuffer);
  COMRELEASE(pIndexBuffer);
  COMRELEASE(pConstantBuffer);
  COMRELEASE(pDeviceContext);
  COMRELEASE(pBlendState);
  COMRELEASE(pDepthState);
  COMRELEASE(pRasterizerState);
  COMRELEASE(pSamplerState);
}

D3D11Renderer::~D3D11Renderer()
{
  reset();
}

HWND D3D11Renderer::getDeviceWindow(IDXGISwapChain* pSwapChain)
{
  DXGI_SWAP_CHAIN_DESC scd;
  if (SUCCEEDED(pSwapChain->GetDesc(&scd)))
    return scd.OutputWindow;
  return NULL;
}

void D3D11Renderer::onDestroy(HWND hWnd)
{
  if (pCurSwapChain && getDeviceWindow(pCurSwapChain) == hWnd)
    reset();
}

bool D3D11Renderer::updateTexture(BaseTexture* btex, ImageHeader* image)
{
  D3D11Texture* tex = (D3D11Texture*) btex;
  int size = tex->size;
  int sizei = 0;
  if (!tex->pTexture || image->updated)
  {
    size = 1;
    while (size < image->width || size < image->height)
      size <<= 1, sizei++;
    if (tex->pTexture && tex->size != size)
    {
      COMRELEASE(tex->pTexture);
      COMRELEASE(tex->pView);
    }
    tex->size = size;
  }
  if (tex->pTexture && tex->pView && !image->updated)
    return true;
  if (tex->pTexture == NULL)
  {
    D3D11_TEXTURE2D_DESC td;
    memset(&td, 0, sizeof td);
    td.Width = tex->size;
    td.Height = tex->size;
    td.MipLevels = 1;
    td.ArraySize = 1;
    td.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    td.SampleDesc.Count = 1;
    td.Usage = D3D11_USAGE_DYNAMIC;
    td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    td.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    if (FAILED(pCurDevice->CreateTexture2D(&td, NULL, &tex->pTexture)))
      return false;
  }
  if (tex->pView == NULL)
  {
    D3D11_SHADER_RESOURCE_VIEW_DESC desc;
    desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    desc.Texture2D.MipLevels = 1;
    desc.Texture2D.MostDetailedMip = 0;
    if (FAILED(pCurDevice->CreateShaderResourceView(tex->pTexture, &desc, &tex->pView)))
      return false;
  }
  D3D11_MAPPED_SUBRESOURCE rc;
  if (FAILED(pDeviceContext->Map(tex->pTexture, 0, D3D11_MAP_WRITE_DISCARD, 0, &rc)))
    return false;
  unsigned char* dst = (unsigned char*) rc.pData;
  unsigned long const* src = state->data->bits(image->offset);
  for (unsigned long y = 0; y < image->height; y++)
  {
    memcpy(dst, src, image->width * 4);
    if (size > image->width) memset(dst + image->width * 4, 0, (size - image->width) * 4);
    dst += rc.RowPitch;
    src += image->width;
  }
  for (unsigned long y = image->height; y < size; y++)
  {
    memset(dst, 0, size * 4);
    dst += rc.RowPitch;
  }
  pDeviceContext->Unmap(tex->pTexture, 0);
  tex->width = image->width;
  tex->height = image->height;
  image->updated = 0;
  return true;
}
void D3D11Renderer::drawRect(BaseTexture* btex, int left, int top, int right, int bottom, unsigned long color)
{
  D3D11Texture* tex = (D3D11Texture*) btex;
  if (tex)
    pDeviceContext->PSSetShaderResources(0, 1, &tex->pView);
  D3D11_MAPPED_SUBRESOURCE rc;
  if (SUCCEEDED(pDeviceContext->Map(pVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &rc)))
  {
    Vertex* v = (Vertex*) rc.pData;
    float x0 = float(left);
    float y0 = float(top);
    float x1 = float(right);
    float y1 = float(bottom);
    float tu = 1;
    float tv = 1;
    if (tex)
    {
      tu = float(tex->width) / float(tex->size);
      tv = float(tex->height) / float(tex->size);
    }
    v[0].x = x0; v[0].y = y0; v[0].u = 0; v[0].v = 0;
    v[1].x = x1; v[1].y = y0; v[1].u = tu; v[1].v = 0;
    v[2].x = x0; v[2].y = y1; v[2].u = 0; v[2].v = tv;
    v[3].x = x1; v[3].y = y1; v[3].u = tu; v[3].v = tv;
    float r = float(color & 0xFF) / 255.0f;
    float g = float((color >> 8) & 0xFF) / 255.0f;
    float b = float((color >> 16) & 0xFF) / 255.0f;
    float a = float((color >> 24) & 0xFF) / 255.0f;
    for (int i = 0; i < 4; i++)
      v[i].z = 0, v[i].r = r, v[i].g = g, v[i].b = b, v[i].a = a;
    pDeviceContext->Unmap(pVertexBuffer, 0);

    pDeviceContext->DrawIndexed(6, 0, 0);
  }
}
void D3D11Renderer::setViewport(int left, int top, int right, int bottom)
{
  D3D11_VIEWPORT viewport;
  viewport.TopLeftX = float(left);
  viewport.TopLeftY = float(top);
  viewport.Width = float(right - left);
  viewport.Height = float(bottom - top);
  viewport.MinDepth = 0.0f;
  viewport.MaxDepth = 1.0f;
  pDeviceContext->RSSetViewports(1, &viewport);

  float xScale = 2.0f / viewport.Width;
  float yScale = 2.0f / viewport.Height;
  float mat[] = {
    xScale,       0, 0, 0,
         0, -yScale, 0, 0,
         0,       0, 1, 0,
    -(viewport.TopLeftX * xScale + 1), viewport.TopLeftY * yScale + 1, 0, 1
  };
  D3D11_MAPPED_SUBRESOURCE rc;
  if (FAILED(pDeviceContext->Map(pConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &rc)))
    return;
  memcpy(rc.pData, mat, sizeof mat);
  pDeviceContext->Unmap(pConstantBuffer, 0);
  pDeviceContext->VSSetConstantBuffers(0, 1, &pConstantBuffer);
}

void D3D11Renderer::render(IDXGISwapChain* pSwapChain, ID3D11Device* pDevice)
{
  SharedData::Lock lock(state->data, 100);
  if (!lock.locked())
    return;
  SharedHeader* hdr = state->data->header();
  if (hdr->hidden)
    return;

  HWND hWnd = getDeviceWindow(pSwapChain);

  if (pDevice != pCurDevice)
  {
    if (pCurSwapChain && hdr->hWnd && IsWindow(hdr->hWnd))
    {
      if (hWnd != hdr->hWnd)
      {
        RECT rcOld, rcNew;
        GetClientRect(hdr->hWnd, &rcOld);
        GetClientRect(hWnd, &rcNew);
        if (rcNew.right < rcOld.right || rcNew.bottom < rcOld.bottom)
          return;
      }
    }
    reset();
    pCurDevice = pDevice;
  }
  pCurSwapChain = pSwapChain;
  hdr->hWnd = hWnd;

  ID3D11DeviceContext* pImmediateContext;
  pDevice->GetImmediateContext(&pImmediateContext);

  if (pDeviceContext == NULL && FAILED(pDevice->CreateDeferredContext(0, &pDeviceContext)))
    return;
  if (pVertexShader == NULL)
  {
    D3D11_INPUT_ELEMENT_DESC desc[3];
    memset(desc, 0, sizeof desc);
    desc[0].SemanticName = "SV_Position";
    desc[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    desc[0].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    desc[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    desc[1].SemanticName = "COLOR";
    desc[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    desc[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    desc[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    desc[2].SemanticName = "TEXCOORD";
    desc[2].Format = DXGI_FORMAT_R32G32_FLOAT;
    desc[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    desc[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    if (FAILED(pDevice->CreateInputLayout(desc, 3, SpriteShader_VS, sizeof SpriteShader_VS, &pInputLayout)) ||
        FAILED(pDevice->CreateVertexShader(SpriteShader_VS, sizeof SpriteShader_VS, NULL, &pVertexShader)))
      return;
  }
  if (pPixelShader == NULL)
  {
    if (FAILED(pDevice->CreatePixelShader(SpriteShader_PS, sizeof SpriteShader_PS, NULL, &pPixelShader)))
      return;
  }
  if (pVertexBuffer == NULL)
  {
    D3D11_BUFFER_DESC desc;
    memset(&desc, 0, sizeof desc);
    desc.ByteWidth = sizeof(Vertex) * 4;
    desc.Usage = D3D11_USAGE_DYNAMIC;
    desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    desc.StructureByteStride = sizeof(Vertex);
    if (FAILED(pDevice->CreateBuffer(&desc, NULL, &pVertexBuffer)))
      return;
  }
  if (pIndexBuffer == NULL)
  {
    unsigned long indices[] = {0, 1, 2, 1, 3, 2};
    D3D11_BUFFER_DESC desc;
    memset(&desc, 0, sizeof desc);
    desc.ByteWidth = sizeof indices;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    desc.StructureByteStride = sizeof indices[0];
    D3D11_SUBRESOURCE_DATA data;
    memset(&data, 0, sizeof data);
    data.pSysMem = indices;
    if (FAILED(pDevice->CreateBuffer(&desc, &data, &pIndexBuffer)))
      return;
  }
  if (pConstantBuffer == NULL)
  {
    D3D11_BUFFER_DESC desc;
    memset(&desc, 0, sizeof desc);
    desc.ByteWidth = sizeof(float) * 16;
    desc.Usage = D3D11_USAGE_DYNAMIC;
    desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    desc.StructureByteStride = sizeof(float) * 16;
    if (FAILED(pDevice->CreateBuffer(&desc, NULL, &pConstantBuffer)))
      return;
  }
  if (pBlendState == NULL)
  {
    D3D11_BLEND_DESC desc;
    memset(&desc, 0, sizeof desc);
    desc.RenderTarget[0].BlendEnable = TRUE;
    desc.RenderTarget[0].SrcBlend = desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA;
    desc.RenderTarget[0].DestBlend = desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
    desc.RenderTarget[0].BlendOp = desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
    desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
    if (FAILED(pDevice->CreateBlendState(&desc, &pBlendState)))
      return;
  }
  if (pDepthState == NULL)
  {
    D3D11_DEPTH_STENCIL_DESC desc;
    memset(&desc, 0, sizeof desc);
    desc.DepthEnable = FALSE;
    desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
    desc.DepthFunc = D3D11_COMPARISON_ALWAYS;
    if (FAILED(pDevice->CreateDepthStencilState(&desc, &pDepthState)))
      return;
  }
  if (pSamplerState == NULL)
  {
    D3D11_SAMPLER_DESC desc;
    memset(&desc, 0, sizeof desc);
    desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
    desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
    desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
    desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
    desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
    if (FAILED(pDevice->CreateSamplerState(&desc, &pSamplerState)))
      return;
  }
  if (pRasterizerState == NULL)
  {
    D3D11_RASTERIZER_DESC desc;
    memset(&desc, 0, sizeof desc);
    desc.CullMode = D3D11_CULL_BACK;
    desc.FillMode = D3D11_FILL_SOLID;
    desc.DepthClipEnable = true;
    desc.MultisampleEnable = true;
    if (FAILED(pDevice->CreateRasterizerState(&desc, &pRasterizerState)))
      return;
  }

  UINT stride = sizeof(Vertex);
  UINT offset = 0;

  ID3D11RenderTargetView* pRenderTarget;
  ID3D11DepthStencilView* pDepthStencil;
  pImmediateContext->OMGetRenderTargets(1, &pRenderTarget, &pDepthStencil);
  pDeviceContext->OMSetRenderTargets(1, &pRenderTarget, pDepthStencil);

  pDeviceContext->OMSetBlendState(pBlendState, NULL, 0xFFFFFF);
  pDeviceContext->OMSetDepthStencilState(pDepthState, 0);
  pDeviceContext->RSSetState(pRasterizerState);
  pDeviceContext->IASetVertexBuffers(0, 1, &pVertexBuffer, &stride, &offset);
  pDeviceContext->IASetIndexBuffer(pIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
  pDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
  pDeviceContext->IASetInputLayout(pInputLayout);
  pDeviceContext->VSSetShader(pVertexShader, NULL, 0);
  pDeviceContext->PSSetShader(pPixelShader, NULL, 0);
  pDeviceContext->PSSetSamplers(0, 1, &pSamplerState);

  BaseRenderer::render();

  COMRELEASE(pRenderTarget);
  COMRELEASE(pDepthStencil);

  ID3D11CommandList* pCommandList;
  if (SUCCEEDED(pDeviceContext->FinishCommandList(FALSE, &pCommandList)))
  {
    pImmediateContext->ExecuteCommandList(pCommandList, TRUE);
    COMRELEASE(pCommandList);
  }

  COMRELEASE(pImmediateContext);
}
